/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.sql.standard;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.star.framework.sql.Identifier;
import cn.star.framework.sql.type.JdbcMappingType;
import cn.star.framework.sql.type.ValueType;
import cn.star.framework.type.QueryType;
import cn.star.framework.util.DateConvertHelper;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;

/**
 * Where条件<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
public class Where {
  /** 名称 */
  @Getter @Setter private Identifier name;

  /** {@link QueryType} */
  @Getter @Setter private QueryType type;

  /** 值 */
  @Getter @Setter private Object value;

  /**
   * <a href='#'>star-framework-extension</a><br>
   * 用来替换 {@link Identifier#getName()} {@link #type} :?<br>
   */
  @Getter @Setter private List<String> parameterNames = new ArrayList<>();

  public Where() {}

  public Where(Identifier name, QueryType type) {
    this.name = name;
    this.type = type;
  }

  public Where(Identifier name, QueryType type, Object value) {
    this.name = name;
    this.type = type;
    this.value = value;
  }

  /**
   * 获取列名
   *
   * @return 名
   */
  public String getColumnName() {
    return name.getColumnName();
  }

  @FunctionalInterface
  public interface ConverterFn<X, Y> {
    X convert(Y v);
  }

  /**
   * 获取列值
   *
   * @param converter 列转换函数
   * @return 值
   */
  public Object getColumnValue(ConverterFn<Object, Object> converter) {
    if (value == null) {
      return null;
    }

    // 若存在转换函数则使用转换函数获取值
    if (converter != null) {
      return converter.convert(value);
    }

    // 通用集合处理
    List<Object> values = value instanceof List ? ((List) value) : new ArrayList<>();

    if (ArrayUtil.isArray(value)) {
      values = CollectionUtil.toList((Object[]) value);
    } else {
      values.add(value);
    }

    Assert.isFalse(
        (type == QueryType.BETWEEN || type == QueryType.NOT_BETWEEN) && values.size() < 2,
        "where type [{}], value type support [Array, List], size >= 2",
        type);

    return this.wrapperType(values);
  }

  private static class ValueTypeTuple {
    @Getter private Class<?> type;
    @Getter private ValueType valueType;

    public ValueTypeTuple(Class<?> type, ValueType valueType) {
      this.type = type;
      this.valueType = valueType;
    }
  }

  private ValueTypeTuple getFieldValueType() {
    if (name.getField() != null) {
      Class<?> clazz = name.getField().getType();
      ValueType valueType = ValueType.getValueType(clazz);
      if (valueType != ValueType._none) {
        return new ValueTypeTuple(clazz, valueType);
      }
    } else if (name.getJdbcType() != null) {
      List<Class<?>> types = JdbcMappingType.getJavaTypes(name.getJdbcType());
      for (Class<?> clazz : types) {
        ValueType valueType = ValueType.getValueType(clazz);
        if (valueType != ValueType._none) {
          return new ValueTypeTuple(clazz, valueType);
        }
      }
    }

    return null;
  }

  private Object wrapperType(List<?> values) {
    if (values.isEmpty()) {
      return values;
    }

    ValueTypeTuple valueTypeTuple = this.getFieldValueType();
    if (valueTypeTuple != null) {
      List<Object> list = new ArrayList<>(values.size());
      for (Object o : values) {
        Object val = this.convert(valueTypeTuple.getType(), valueTypeTuple.getValueType(), o);
        list.add(val);
      }
      return list.size() == 1 ? list.get(0) : list;
    }

    return values.size() == 1 ? values.get(0) : values;
  }

  private Object convert(Class<?> type, ValueType valueType, Object val) {
    String str = val.toString();
    switch (valueType) {
      case _byte:
        return Byte.parseByte(str);
      case _short:
        return Short.parseShort(str);
      case _int:
        return Integer.parseInt(str);
      case _long:
        return Long.parseLong(str);
      case _float:
        return Float.parseFloat(str);
      case _double:
        return Double.parseDouble(str);
      case _boolean:
        return Boolean.valueOf(str);
      case _char:
        return str.charAt(0);
      case _string:
        return str;
      case _big_decimal:
        return new BigDecimal(str);
      case _date:
      case _timestamp:
      case _local_time:
      case _local_date:
      case _local_date_time:
      case _instant:
        return DateConvertHelper.convert(type, val);
    }

    return val;
  }
}
